package vcsn;


import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.swt.widgets.Shell;

import vcsn.entities.*;
import vcsn.entities.Class;
import vcsn.entities.Relation.Type;
import vcsn.entities.Variable.Multiplexity;
import vcsn.ui.InputDialog;
import vcsn.utils.ExportImportHandler;

public class RelationMapBuilder {
	public class Visitor extends ASTVisitor{
		String packageName;
		String compilationUnitName;
		List<Class> classEntityList;
		CompilationUnit unit;
		
		public Visitor(String packageName, String compilationUnitName, CompilationUnit compilationUnit){
			this.packageName = packageName;
			this.compilationUnitName = compilationUnitName;
			this.unit = compilationUnit;
			classEntityList = new ArrayList<Class>();
		}
		
//		@Override
//		public boolean visit(MethodInvocation methodInvocation){
//			IMethodBinding methodBind = methodInvocation.resolveMethodBinding();
//			String qualifiedMethodSignature = methodBind.getDeclaringClass().getQualifiedName() + "." + methodBind.getName() + Config.separator;
//			for(int i = 0; i < methodInvocation.arguments().size(); i++){
//				qualifiedMethodSignature += ((org.eclipse.jdt.core.dom.Expression)methodInvocation.arguments().get(i)).toString();
//			}			
//			System.out.println(methodInvocation.getStartPosition() + methodInvocation.getLength() - (methodInvocation.getName().getLength() + methodInvocation.getName().getStartPosition()));
//			System.out.println(methodInvocation.toString() + " : " + methodInvocation.getStartPosition());
//			return true;
//		}
		
		@Override
		public boolean visit(TypeDeclaration typeDeclaration){
			Class classEntity = new Class();
			ITypeBinding typeBind = typeDeclaration.resolveBinding();
	        org.eclipse.jdt.core.dom.Type superClass = typeDeclaration.getSuperclassType();
	        classEntity.id = typeBind.getQualifiedName();
	        classEntity.filename = this.compilationUnitName;
	        classEntity.declarationLineNumber = unit.getLineNumber(typeDeclaration.getName().getStartPosition());
	        classEntity.modifier = typeBind.getModifiers();
	        
	        classEntity.generalizationAndRealization = (superClass != null) ? new Relation[typeDeclaration.superInterfaceTypes().size() + 1]:new Relation[typeDeclaration.superInterfaceTypes().size()];
	        if(superClass != null){
	        	classEntity.generalizationAndRealization[0] = new Relation();
	        	classEntity.generalizationAndRealization[0].type = Type.GENERALIZATION;
	        	classEntity.generalizationAndRealization[0].targetId = superClass.resolveBinding().getQualifiedName();
	        	classEntity.generalizationAndRealization[0].declarationLineNumber = unit.getLineNumber(superClass.getStartPosition());
	        	
	        }
	        
        	for(int i = (superClass != null) ? 1 : 0, j = 0; j < typeDeclaration.superInterfaceTypes().size();i++, j++){
        		org.eclipse.jdt.core.dom.Type interfaceBind = (org.eclipse.jdt.core.dom.Type) typeDeclaration.superInterfaceTypes().get(j);
        		
        		classEntity.generalizationAndRealization[i] = new Relation();
        		classEntity.generalizationAndRealization[i].type = Type.REALIZATION;
        		classEntity.generalizationAndRealization[i].targetId = interfaceBind.resolveBinding().getQualifiedName();
        		classEntity.generalizationAndRealization[0].declarationLineNumber = unit.getLineNumber(interfaceBind.getStartPosition());
        	}
	        FieldDeclaration[] fieldDeclaration = typeDeclaration.getFields();
//	        classEntity.variables = new Variable[fieldDeclaration.length];
	        List<Variable> variableList = new ArrayList<Variable>(); 
	        for(int i = 0; i < fieldDeclaration.length; i++){
	        	for(int j = 0; j < fieldDeclaration[i].fragments().size();j++){
	        		VariableDeclarationFragment fragment = (VariableDeclarationFragment) fieldDeclaration[i].fragments().get(j);
		        	ITypeBinding variableBind = fieldDeclaration[i].getType().resolveBinding();
		        	Variable variable = new Variable();
		        	variable.id = typeBind.getQualifiedName() + "." + fragment.getName().getFullyQualifiedName();
		        	variable.declarationLineNumber = unit.getLineNumber(fragment.getStartPosition());
		        	variable.association = new Relation();
		        	variable.association.type = Type.ASSCOCIATION;
		        	variable.association.targetId = variableBind.getQualifiedName();
		        	variable.modifier = variableBind.getModifiers();
		        	
		        	if(variableBind.isParameterizedType() || variableBind.isArray()){
		        		variable.multiplexity = Multiplexity.Multi;
		        	}else{
		        		variable.multiplexity = Multiplexity.One;
		        	}
		        	variableList.add(variable);
	        	}
	        }
	        classEntity.variables = new Variable[variableList.size()];
	        for(int i = 0; i < classEntity.variables.length;i++){
	        	classEntity.variables[i] = variableList.get(i);
	        }
	        
	        MethodDeclaration[] methodDeclaration =  typeDeclaration.getMethods();
	        classEntity.methods = new Method[methodDeclaration.length];
        	for(int i = 0; i < methodDeclaration.length; i++){
        		IMethodBinding methodBind = methodDeclaration[i].resolveBinding();
        		classEntity.methods[i] = new Method();
        		classEntity.methods[i].id = typeBind.getQualifiedName() + "." + methodBind.getName();
        		classEntity.methods[i].declarationLineNumber = unit.getLineNumber(methodDeclaration[i].getStartPosition());
        		classEntity.methods[i].modifier = methodBind.getModifiers();
        		
        		ITypeBinding[] parameterBinds = methodBind.getParameterTypes();
        		classEntity.methods[i].parameterDependency = new Relation[parameterBinds.length];
        		for(int j = 0; j < parameterBinds.length; j++){
        			classEntity.methods[i].parameterDependency[j] = new Relation();
        			classEntity.methods[i].parameterDependency[j].type = Type.DEPENDENCY;
        			classEntity.methods[i].parameterDependency[j].targetId =parameterBinds[j].getQualifiedName();
        			classEntity.methods[i].parameterDependency[j].declarationLineNumber = unit.getLineNumber(methodDeclaration[i].getStartPosition());
        		}
        		org.eclipse.jdt.core.dom.Type returnType = methodDeclaration[i].getReturnType2();
        		if(!methodDeclaration[i].isConstructor()){
        			ITypeBinding returnTypeBind = returnType.resolveBinding();
        			classEntity.methods[i].returnValueDependency = new Relation();
        			classEntity.methods[i].returnValueDependency.type = Type.DEPENDENCY;
        			classEntity.methods[i].returnValueDependency.targetId = returnTypeBind.getQualifiedName();
        			classEntity.methods[i].returnValueDependency.declarationLineNumber = unit.getLineNumber(returnType.getStartPosition());
        		}else{
        			classEntity.methods[i].returnValueDependency = null;
        		}
        	}
        	classEntityList.add(classEntity);
			return true;
		}
		
		public List<Class> getClassEntityList(){
			return classEntityList;
		}
	}
	
	private IJavaProject project;
	private List<Class> oldRelationMap;
	private List<Class> relationMap;
	private String commitMessage;
	private File exportedRelationMapDiffList;
	private File fDiffPatch;
	private VectorClock vectorClock;
	private boolean isCancel;
	private Configuration configure;
	
	public List<Class> getOldRelationMap(){ return oldRelationMap; }
	public List<Class> getRelationMap(){ return relationMap; }
	public File getexportedRelationMapDiffList(){ return exportedRelationMapDiffList; }
	public File getfDiffPatch(){ return fDiffPatch; }
	public boolean getIsCancel(){ return isCancel; }
	
	public RelationMapBuilder(IProject p, String message) throws Exception {
		if (!p.isNatureEnabled("org.eclipse.jdt.core.javanature"))
			throw new Exception("cannot instant Main class");
		
		project = JavaCore.create(p);
		configure = new Configuration(p.getLocation().toString(), p.getName());
		commitMessage = message;
		isCancel = (commitMessage.isEmpty())? true : false;
	}
	
	public RelationMapBuilder(IProject p, Shell shell) throws Exception {
		if (!p.isNatureEnabled("org.eclipse.jdt.core.javanature"))
			throw new Exception("cannot instant Main class");
		
		project = JavaCore.create(p);
		configure = new Configuration(p.getLocation().toString(), p.getName());
		commitMessage = "";
		InputDialog inputDialog = new vcsn.ui.InputDialog(shell);
		inputDialog.setHeader("Please Enter Commit Message:");
        inputDialog.open();
        commitMessage = inputDialog.getInputString();
        isCancel = (commitMessage.isEmpty())? true : false;
	}
	
	public void cleanbuild() throws Exception {
		for (IPackageFragment mypackage : project.getPackageFragments()) {
			if (mypackage.getKind() == IPackageFragmentRoot.K_SOURCE) {
				for (ICompilationUnit unit : mypackage.getCompilationUnits()) {
					// Now create the AST for the ICompilationUnits
					CompilationUnit parse = parse(unit);
					Visitor visitor = new Visitor(mypackage.getElementName(), unit.getPath().toString(), parse);
					parse.accept(visitor);
					addClassEntityListToRelationMap(visitor.getClassEntityList());
				}

			}

		}
		
		vectorClock = null;
		vectorClock = ExportImportHandler.importVectorClock(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.localClockFile, project.getProject().getName());
		if(vectorClock == null){
			vectorClock = new VectorClock();
			vectorClock.incrementClock(configure.getUserName() + Config.keyConnector + configure.getUserEmail());
		}else{
			vectorClock.incrementClock(configure.getUserName() + Config.keyConnector + configure.getUserEmail());
		}

		ExportImportHandler.exportRelationMapToXML(relationMap, project.getProject().getName());
		ExportImportHandler.exportVectorClock(vectorClock, project.getProject().getName());
	}
	
	public void build() throws Exception {
		for (IPackageFragment mypackage : project.getPackageFragments()) {
			if (mypackage.getKind() == IPackageFragmentRoot.K_SOURCE) {
				for (ICompilationUnit unit : mypackage.getCompilationUnits()) {
					// Now create the AST for the ICompilationUnits
					CompilationUnit parse = parse(unit);
					Visitor visitor = new Visitor(mypackage.getElementName(), unit.getPath().toString(), parse);
					parse.accept(visitor);
					addClassEntityListToRelationMap(visitor.getClassEntityList());
				}

			}

		}

//		String authorName = configure.getUserName();
		oldRelationMap = ExportImportHandler.importRelationMapFromXML(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.relaitonMapFile, project.getProject().getName());
		vectorClock = null;
		vectorClock = ExportImportHandler.importVectorClock(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.localClockFile, project.getProject().getName());
		if(vectorClock == null){
			vectorClock = new VectorClock();
			vectorClock.incrementClock(configure.getUserName() + Config.keyConnector + configure.getUserEmail());
		}else{
			vectorClock.incrementClock(configure.getUserName() + Config.keyConnector + configure.getUserEmail());
		}

		ExportImportHandler.exportRelationMapToXML(relationMap, project.getProject().getName());
		List<RelationMapDiff> relationMapDiffList = new RelationMapComparer().compare(oldRelationMap, relationMap);
		exportedRelationMapDiffList = ExportImportHandler.exportRelationMapDiffToXML(relationMapDiffList, project.getProject().getLocation().toString());
		ExportImportHandler.exportVectorClock(vectorClock, project.getProject().getName());

		//clearRelationMap(this.relationMap);
	}
	
	public void buildAndBroadcastCommitPatch() throws Exception{
		build();
		broadcastCommitPatch();
	}
	
	public void broadcastCommitPatch() throws Exception{
		String authorName = configure.getUserName();
		Git git = Git.open(new File(project.getProject().getLocation().toString()));

        Repository repository = git.getRepository();
        Ref head = repository.getRef(Config.HEADHash);
        // a RevWalk allows to walk over commits based on some filtering that is defined.
        RevWalk walk = new RevWalk(repository);
        RevCommit commit = walk.parseCommit(head.getObjectId());
        RevTree tree = commit.getTree();
        // now use a TreeWalk to iterate over all files in the Tree recursively
        // you can set Filters to narrow down the results if needed
        TreeWalk treeWalk = new TreeWalk(repository);
        treeWalk.addTree(tree);
        treeWalk.setRecursive(true);
        while (treeWalk.next()) {
            if(!new File(project.getProject().getLocation().toString() + File.separator + treeWalk.getPathString()).exists()){
            	git.rm().addFilepattern(treeWalk.getPathString()).call();
            }
        }
        // Add all file into the commit
		git.add().addFilepattern(".").call();
		git.commit().setAuthor(authorName, configure.getUserEmail()).setMessage(commitMessage).call();
		git.push().setRemote(configure.getRemoteURI()).setForce(true).call();
		
		LastAppliedCommitHashList commithashList = ExportImportHandler.importLastAppliedCommitHashList(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.localClockFile, project.getProject().getName());
		if(commithashList == null){
			commithashList = new LastAppliedCommitHashList();
		}
		commithashList.setCommitHash(authorName, head.getObjectId().getName());
		ExportImportHandler.exportLastAppliedCommitHashList(commithashList, project.getProject().getName());

		// broadcast the committed patch and relation map.
		ClientThread client = new ClientThread();
		String localAddress = client.getLocalAddress(new File(project.getProject().getLocation().toString() + File.separator + Config.peerFile));
		client.writeHeaderIntoPackageFile(project.getProject().getName(), vectorClock, localAddress, Config.HeaderType.COMMITPATCH);
		client.writeFileIntoPackageFile(exportedRelationMapDiffList);
		client.writeStringIntoPackageFile(authorName);
		client.writeStringIntoPackageFile(repository.getRef(Config.HEADHash).getObjectId().getName());
		client.flush(new File(project.getProject().getLocation().toString() + File.separator + Config.peerFile));
		client.disposePackage();
	}
	
	public void addClassEntityListToRelationMap(List<Class> classEntityList){
		if(relationMap == null){
			relationMap = classEntityList;
		}else{
			for(Class classEntity : classEntityList){
				addClassEntityToRelationMap(classEntity);
			}
		}
	}
	
	public void addClassEntityToRelationMap(Class classEntity){
		if(relationMap == null){
			relationMap = new ArrayList<Class>();
		}
		relationMap.add(classEntity);
	}
		
	public void clearRelationMap(List<Class> map){
		map = null;
	}
	
	private static CompilationUnit parse(ICompilationUnit unit) {
        ASTParser parser = ASTParser.newParser(AST.JLS4);
        parser.setKind(ASTParser.K_COMPILATION_UNIT);
        parser.setSource(unit);
        parser.setResolveBindings(true);
        return (CompilationUnit) parser.createAST(null); // parse
    }
}
